package me.sealer.ssm.service.impl;

import com.github.pagehelper.PageHelper;
import lombok.extern.slf4j.Slf4j;
import me.sealer.ssm.constant.ErrorCodeConstant;
import me.sealer.ssm.constant.PageConstant;
import me.sealer.ssm.constant.RoleConstant;
import me.sealer.ssm.constant.Sex;
import me.sealer.ssm.constant.UserOrigin;
import me.sealer.ssm.exception.SsmException;
import me.sealer.ssm.mapper.RoleMapper;
import me.sealer.ssm.mapper.UserMapper;
import me.sealer.ssm.mapper.UserRoleMapper;
import me.sealer.ssm.model.Role;
import me.sealer.ssm.model.RoleExample;
import me.sealer.ssm.model.User;
import me.sealer.ssm.model.UserExample;
import me.sealer.ssm.model.UserRole;
import me.sealer.ssm.page.PageResult;
import me.sealer.ssm.service.UserService;
import me.sealer.ssm.shiro.authc.OpenAuth2Token;
import me.sealer.ssm.shiro.authc.PhoneToken;
import me.sealer.ssm.utils.PageUtil;
import me.sealer.ssm.utils.ReflectUtil;
import me.sealer.ssm.utils.StringUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.HostAuthenticationToken;
import org.apache.shiro.authc.RememberMeAuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Assert;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.Random;

/**
 * @author sealer
 * @date 2016/09/27
 */
@Slf4j
@Service
public class UserServiceImpl implements UserService {
    private final UserMapper userMapper;
    private final RoleMapper roleMapper;
    private final UserRoleMapper userRoleMapper;

    public UserServiceImpl(UserMapper userMapper, RoleMapper roleMapper, UserRoleMapper userRoleMapper) {
        this.userMapper = userMapper;
        this.roleMapper = roleMapper;
        this.userRoleMapper = userRoleMapper;
    }

    @Override
    public PageResult<User> queryUsersPage(String username, int pageNo, int pageSize) {
        pageSize = Math.min(pageSize, PageConstant.MAX_PAGE_SIZE);

        //调用mapper类中的selectByExample方法，如果传入类型为null，则表示无条件查找
        UserExample userExample = genUserExampleIgNonNull(username);

        int count = userMapper.countByExample(userExample);

        PageHelper.startPage(pageNo, pageSize);
        List<User> users = userMapper.selectByExample(userExample);

        return PageUtil.generatePageResult(pageNo, pageSize, users, count);
    }

    @Override
    public User queryUserByUsername(String username) {
        UserExample userExample = new UserExample();
        userExample.createCriteria().andUsernameEqualTo(username);
        List<User> users = userMapper.selectByExample(userExample);

        if (users.size() == 0) {
            return null;
        }

        Assert.isTrue(users.size() == 1, "脏数据异常");

        return users.get(0);
    }

    @Override
    public User queryUserByPhoneNumber(String phoneNumber) {
        UserExample userExample = new UserExample();
        userExample.createCriteria().andPhoneNumberEqualTo(phoneNumber);
        List<User> users = userMapper.selectByExample(userExample);
        if (users.size() == 0) {
            return null;
        }

        Assert.isTrue(users.size() == 1, "脏数据异常");

        return users.get(0);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createUser(User user) {
        // 用户已存在， 不能注册
        User u = this.queryUserByUsername(user.getUsername());
        if (u != null) {
            throw new SsmException(ErrorCodeConstant.USER_USER_ALREADY_EXISTS, new Object[]{user.getUsername()});
        }

        String password = user.getPassword();
        user.setPassword(null);
        user.setId(null);

        // 新增用户基本数据
        userMapper.insertSelective(user);

        User userSelected = this.queryUserByUsername(user.getUsername());
        int userId = userSelected.getId();

        // 检验通过， 修改密码， 更新数据库。
        String passwordHex = DigestUtils.md5DigestAsHex((userId + password).getBytes(StandardCharsets.UTF_8));
        updateUserPassword(userId, passwordHex);

        // 查询normal role
        RoleExample roleExample = new RoleExample();
        roleExample.createCriteria().andNameEqualTo(RoleConstant.ROLE_NORMAL);
        List<Role> roles = roleMapper.selectByExample(roleExample);

        Assert.isTrue(roles.size() == 1, "脏数据异常");

        Role role = roles.get(0);

        UserRole userRole = UserRole.builder()
                .roleId(role.getId())
                .userId(userId)
                .build();
        userRoleMapper.insert(userRole);
    }

    @Override
    public User queryUserByEmail(String email) {
        UserExample userExample = new UserExample();
        userExample.createCriteria().andEmailEqualTo(email);
        List<User> users = userMapper.selectByExample(userExample);

        if (users.size() == 0) {
            return null;
        }

        Assert.isTrue(users.size() == 1, "脏数据异常");

        return users.get(0);
    }

    @Override
    public void modifyUserPassword(String oldPassword, String password, String confirmPassword, String username) {
        User user = this.queryUserByUsername(username);

        String oldRealPasswordHex = user.getPassword();
        int saltId = user.getId();

        // 判断二次输入的新确认密码是否与新设置的密码相同
        boolean passNewConfirmEquals = StringUtil.equals(password, confirmPassword);
        if (!passNewConfirmEquals) {
            throw new SsmException(ErrorCodeConstant.USER_PASSWORD_CONFIRM_WRONG);
        }

        // 判断输入的旧密码是否一致
        String oldPasswordHex = DigestUtils.md5DigestAsHex((saltId + oldPassword).getBytes((StandardCharsets.UTF_8)));
        boolean isPermitted = StringUtil.equals(oldRealPasswordHex, oldPasswordHex);
        if (!isPermitted) {
            throw new SsmException(ErrorCodeConstant.USER_LOGIN_PASSWORD_WRONG);
        }

        // 判断新老密码是否相同
        boolean passNewOldEquals = StringUtil.equals(oldPassword, password);
        if (passNewOldEquals) {
            throw new SsmException(ErrorCodeConstant.USER_PASSWORD_NEW_OLD_SAME);
        }

        // 检验通过， 修改密码， 更新数据库， 登出弹窗， 重新登录。
        String newPasswordHex = DigestUtils.md5DigestAsHex((saltId + password).getBytes(StandardCharsets.UTF_8));
        updateUserPassword(saltId, newPasswordHex);
    }

    @Override
    public void quickLogin(String phoneNumber) {
        User user = this.queryUserByPhoneNumber(phoneNumber);

        // 正常注册流程已经限制不能使用纯数字， 所以这里使用手机号作为用户名。 约定。
        // 密码初始值为 ssm_123456, 快捷注册成功后发送短信通知尽快修改。约定。
        if (user == null) {
            User newUser = User.builder()
                    // 默认用户名
                    .username(phoneNumber)
                    // 默认密码 ssm_123456
                    .password("ssm_123456")
                    .phoneNumber(phoneNumber)
                    // 默认性别保密
                    .sex(Sex.UNKNOWN)
                    .origin(UserOrigin.PHONE)
                    .build();

            // 反射方式调用事务方法可触发事务
            ReflectUtil.call(this, "createUser", newUser);

            // TODO 短信通知用户临时密码及临时用户名
            user = this.queryUserByPhoneNumber(phoneNumber);
        }

        Subject subject = SecurityUtils.getSubject();
        String username = user.getUsername();
        PhoneToken phoneToken = new PhoneToken(username, phoneNumber);

        boolean isPermitted = shiroLoginSuccessfully(subject, phoneToken);
        if (!isPermitted) {
            throw new SsmException(ErrorCodeConstant.USER_LOGIN_FAIL);
        }
    }

    @Override
    public void login(String username, String password, Boolean rememberMe) {
        Subject subject = SecurityUtils.getSubject();

        boolean isPermitted = true;
        if (StringUtil.isNullOrEmpty(username) || StringUtil.isNullOrEmpty(password)) {
            isPermitted = false;
        }
        if (isPermitted) {
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
            if (rememberMe != null) {
                token.setRememberMe(rememberMe);
            }
            isPermitted = shiroLoginSuccessfully(subject, token);
        }

        if (!isPermitted) {
            throw new SsmException(ErrorCodeConstant.USER_LOGIN_FAIL);
        }
    }

    @Override
    public void oauth2Login(String username, String origin, String userAvatarUrl) {
        User user = this.queryUserByUsername(username);

        boolean neededCreate = false;
        if (user != null) {
            // 用户名存在， 且未从github登录过， 则再生成一个唯一用户名
            if (!Objects.equals(UserOrigin.GITHUB, user.getOrigin())) {
                String usernameSuffix = "";
                // 保证用户名不存在
                while (user != null) {
                    usernameSuffix = "_" + new Random().nextInt();
                    user = this.queryUserByUsername(username + usernameSuffix);
                }
                username += usernameSuffix;

                neededCreate = true;
            }
        } else {
            neededCreate = true;
        }

        if (neededCreate) {
            // 正常注册流程已经限制不能使用纯数字， 所以这里使用手机号作为用户名。 约定。
            // 密码初始值为 ssm_123456, 快捷注册成功后发送短信通知尽快修改。约定。

            User newUser = User.builder()
                    // 默认用户名
                    .username(username)
                    // 默认密码 ssm_123456
                    .password("ssm_123456")
                    // 默认性别保密
                    .sex(Sex.UNKNOWN)
                    .origin(UserOrigin.GITHUB)
                    .avatarUrl(userAvatarUrl)
                    .build();

            // 反射方式调用事务方法可触发事务
            ReflectUtil.call(this, "createUser", newUser);
        }

        Subject subject = SecurityUtils.getSubject();
        OpenAuth2Token openAuth2Token = new OpenAuth2Token(username, username);
        boolean permitted = shiroLoginSuccessfully(subject, openAuth2Token);
        if (!permitted) {
            throw new SsmException(ErrorCodeConstant.USER_LOGIN_FAIL);
        }
    }


    /**
     * 更新用户密码
     *
     * @param userId      用户id
     * @param passwordHex 密码
     */
    private void updateUserPassword(int userId, String passwordHex) {
        userMapper.updateByPrimaryKeySelective(User.builder().id(userId).password(passwordHex).build());
    }

    /**
     * 获取userExample non null
     *
     * @param username 用户名
     * @return userExample
     */
    private UserExample genUserExampleIgNonNull(String username) {
        UserExample userExample = new UserExample();
        UserExample.Criteria criteria = userExample.createCriteria();
        if (username != null && !"".equals(username)) {
            criteria.andUsernameLike("%" + username + "%");
        }

        return userExample;
    }

    /**
     * shiro login
     *
     * @param subject shiro subject
     * @param token   shiro token
     * @return login success or fail
     */
    private <T extends HostAuthenticationToken & RememberMeAuthenticationToken> boolean shiroLoginSuccessfully(Subject subject, T token) {
        boolean loginSuccessfully = true;
        try {
            subject.login(token);
            if (log.isDebugEnabled()) {
                Session session = subject.getSession(false);
                log.debug("session id is : " + session.getId());
                session.getAttributeKeys().forEach(key ->
                        log.debug(key + " : " + session.getAttribute(key)));
            }
        } catch (Exception e) {
            log.error(e.toString());
            loginSuccessfully = false;
        }
        return loginSuccessfully;
    }
}
